package genetica.cromosomas.fenotipos.fenotipoPG;

import java.util.ArrayList;

public class TreePG implements Cloneable {

	static public int profundidadMaxima;
	protected ArrayList<TreePG> hijos;
	protected TreePG padre;
	protected int nivel;
	protected Nodo nodo;
	protected int profundidadArbol = 0;

	public TreePG(TreePG other) {
		this.nodo = other.nodo;
		this.nivel = other.nivel;
		// TODO clonar los hijos? SI
		this.hijos = new ArrayList<TreePG>();
		this.profundidadArbol = other.profundidadArbol;
		for (TreePG hijo : other.hijos) {
			TreePG nuevoHijo = hijo.clone();
			this.hijos.add(nuevoHijo);
			nuevoHijo.setPadre(this);
		}
		// no debo clonar el padre porque si no guardo el padre antiguo

		// this.padre = other.padre;
		this.padre = null;
	}

	public int getProfundidadArbol() {
		return profundidadArbol;
	}

	public int getNivel() {
		return nivel;
	}

	public Nodo getNodo() {
		return nodo;
	}

	public void setNodo(Nodo nodo) {
		this.nodo = nodo;
	}

	public TreePG(Nodo nodo) {
		this.nodo = nodo;
		this.nivel = 0;
		this.padre = null;
		profundidadArbol = 0;
		this.hijos = new ArrayList<TreePG>();

	}

	public boolean esRaiz() {
		return (this.padre == null);
	}

	public void addHijo(TreePG hijo) {
		if (hijo.profundidadArbol + this.nivel >= profundidadMaxima) {
			int prof = hijo.profundidadArbol + this.nivel;
			throw new Error("Se ha alcanzado la profundidad máxima . Maximo: "
					+ profundidadMaxima + ". NivelAlcanzado: " + prof);
		}
		hijo.nivel = this.nivel + 1;
		hijo.padre = this;
		hijos.add(hijo);
		this.setProfundidad(hijo.profundidadArbol + 1);
	}

	public void setHijo(TreePG hijo, int pos) {
		if (hijo.profundidadArbol + this.nivel >= profundidadMaxima)
			throw new Error("Se ha alcanzado la profundidad máxima "
					+ profundidadMaxima + " <= "
					+ (hijo.profundidadArbol + this.nivel + 1));
		hijo.nivel = this.nivel + 1;
		hijo.padre = this;
		hijos.set(pos, hijo);
		this.profundidadArbol = 0;
		for (TreePG _hijo : this.getHijos()) {
			this.setProfundidad(_hijo.profundidadArbol + 1);
		}
	}

	public ArrayList<TreePG> getHijos() {
		return hijos;
	}

	public void setNivel(int nivel) {
		this.nivel = nivel;
	}

	public void setProfundidad(int profundidad) {
		if (this.profundidadArbol < profundidad) {
			this.profundidadArbol = profundidad;
			if (padre != null)
				padre.setProfundidad(padre.profundidadArbol + 1);
		}
	}

	public TreePG getHijo(int numHijo) {
		if (numHijo >= 0 && numHijo < hijos.size())
			return hijos.get(numHijo);
		return null;
	}

	public boolean esHoja() {
		Class<?> superClass = this.nodo.getClass().getSuperclass();
		if (superClass.equals(Terminal.class))
			return true;
		return false;
		// boolean finded = false;
		// for (int i = 0; i < interfaces.length && !finded; i++) {
		// Class<?> interface_ = interfaces[i];
		// // System.out.println(interface_.toString());
		// if (interface_.equals(Terminal.class)) {
		// return true;
		// }
		// }
		// return false;
	}

	@Override
	public TreePG clone() {
		return new TreePG(this);
	}

	public Koza evalua(Koza koza) {
		// ignoramos el retorno, solo nos interesa como queda la koza
		this.evaluaNodo(koza);
		return koza;
	}

	public Object evaluaNodo(Koza koza) {
		// comprobar hijos
		// calcular hijos.
		Nodo nodo = this.getNodo();
		ArrayList<TreePG> hijos = this.getHijos();
		if (nodo instanceof BS) {
			if (hijos.size() != 0)
				throw new Error(
						"Arbol mal consutruido para el nodo BS, número ("
								+ hijos.size()
								+ ") de hijos incorrectos. Esperado 0");
			return BS.ejecutar(koza);
		}
		if (nodo instanceof CP) {
			if (hijos.size() != 0)
				throw new Error(
						"Arbol mal consutruido para el nodo CP, número ("
								+ hijos.size()
								+ ") de hijos incorrectos. Esperado 0");
			return CP.ejecutar(koza);
		}
		if (nodo instanceof DU) {
			if (hijos.size() != 2)
				throw new Error(
						"Arbol mal consutruido para el nodo DU, número ("
								+ hijos.size()
								+ ") de hijos incorrectos. Esperado 2");
			return DU.ejecutar(koza, hijos.get(0), hijos.get(1));
		}
		if (nodo instanceof EQ) {
			if (hijos.size() != 2)
				throw new Error(
						"Arbol mal consutruido para el uno que conodo EQ, número ("
								+ hijos.size()
								+ ") de hijos incorrectos. Esperado 2");
			return EQ.ejecutar(koza, hijos.get(0), hijos.get(1));
		}
		if (nodo instanceof MM) {
			if (hijos.size() != 1)
				throw new Error(
						"Arbol mal consutruido para el nodo MM, número ("
								+ hijos.size()
								+ ") de hijos incorrectos. Esperado 1");
			TreePG b = hijos.get(0);
			if (!(esTerminal(b.getNodo()))) {
				throw new Error(
						"Arbol mal consutruido para el nodo MP, hijo incorrecto");
			}
			// TODO tendría que hacer cast aunque no es necesario
			return MM.ejecutar(koza, b);
		}
		if (nodo instanceof MP) {
			if (hijos.size() != 1)
				throw new Error(
						"Arbol mal consutruido para el nodo MP, número ("
								+ hijos.size()
								+ ") de hijos incorrectos. Esperado 1");
			TreePG b = hijos.get(0);
			if (!(esTerminal(b.getNodo()))) {
				throw new Error(
						"Arbol mal consutruido para el nodo MP, hijo incorrecto");
			}
			// TODO tendría que hacer cast aunque no es necesario
			return MP.ejecutar(koza, b);
		}
		if (nodo instanceof NOT) {
			if (hijos.size() != 1)
				throw new Error(
						"Arbol mal consutruido para el nodo NOT, número ("
								+ hijos.size()
								+ ") de hijos incorrectos. Esperado 1");
			return NOT.ejecutar(koza, hijos.get(0));
		}
		if (nodo instanceof SN) {
			if (hijos.size() != 0)
				throw new Error(
						"Arbol mal consutruido para el nodo SN, número ("
								+ hijos.size()
								+ ") de hijos incorrectos. Esperado 0");
			return SN.ejecutar(koza);
		}

		throw new Error("Operacion no soportada");
	}

	@Override
	public String toString() {
		String cadena;
		cadena = this.nodo.toString() + " ";
		if (this.hijos.size() > 0) {
			cadena += "( ";
			for (int i = 0; i < this.hijos.size(); i++) {
				cadena += this.hijos.get(i) + " ";
			}
			cadena += ") ";
		}
		return cadena;
	}

	public boolean esTerminal(Nodo nodo) {
		if (nodo == null)
			return false;
		Class<?> superClass = nodo.getClass().getSuperclass();
		if (superClass.equals(Terminal.class))
			return true;
		return false;
	}

	public TreePG getPadre() {
		return padre;
	}

	public void setPadre(TreePG padre) {
		this.padre = padre;
	}

	public int numFunciones() {
		if (this.esHoja())
			return 0;
		int acumulado = 0;
		for (int i = 0; i < this.hijos.size(); i++)
			acumulado += hijos.get(i).numFunciones();
		return acumulado + 1;
	}

	public void resetProfundidad() {
		this.profundidadArbol = 0;
		// TODO Auto-generated method stub
		
	}
}
